import weakref
import pyglet
from pyglet.gl import *
from vec2d import Vec2d

import resource
from fastsprite import Sprite
from robots import Robot
from util import Rectangle

class Tower(object):
    """Abstract base class for all towers to inherit from."""
    def __init__(self):
        self.map = None
        self.reload_timer = 0
        self.state = 'normal'

        self.firing_policy = 'first'  # or 'last' or 'weakest' or 'strongest'
        self.time_to_reload = 0.5

        self.rotation = 0

        # For drawing area of effect
        self.circle = Sprite(resource.texture('graphics/disc128.png', 64, 64))

        # Derived class needs to:
        # 1. set up sprites
        # 3. set area of effect radius
        # 4. override update and shoot_target methods

    def set_area_of_effect_radius(self, radius):
        self.aoe_radius = radius
        self.aoe_radius_squared = self.aoe_radius ** 2
        self.circle.scale = self.aoe_radius / 64.0

    def draw_area_of_effect(self, valid=True):
        """Draw a circle showing area where tower can attack."""
        self.circle.position = self.position
        if valid: glColor4f(1,1,1,0.5)
        else: glColor4f(1,0,0,0.5)
        self.circle.draw()
        glColor3f(1,1,1)
        
    def draw(self):
        self.sprite.rotation = self.rotation
        self.sprite.position = self.position
        self.sprite.draw()

    def rectangle(self):
        return Rectangle(self.position.x + self.shape.x, self.position.y + self.shape.y, self.shape.width, self.shape.height)

    def shoot(self):
        """Find and shoot at a target.  Derived classes should not change this method.
        Instead override the shoot_target method to set tower effects."""
        # Check that enough time has passed since last shot.
        if self.reload_timer > 0:
            return

        # Find nearby robots within area of efect.
        within_range = []
        for obj in self.map.objects:
            if isinstance(obj, Robot):
                d = self.position.get_dist_sqrd(obj.position)
                if d < self.aoe_radius_squared:
                    within_range.append(obj)

        # Choose which robot to fire upon.
        if not within_range:
            return
        if len(within_range) == 1:
            target = within_range[0]
        else:
            if self.firing_policy == 'first':
                within_range.sort(key=lambda x:x.id)
                target = within_range[0]
            elif self.firing_policy == 'last':
                within_range.sort(key=lambda x:x.id, reverse=True)
                target = within_range[0]
            elif self.firing_policy == 'weakest':
                within_range.sort(key=lambda x:x.hit_points)
                target = within_range[0]
            elif self.firing_policy == 'strongest':
                within_range.sort(key=lambda x:x.hit_points, reverse=True)
                target = within_range[0]
            
        # Then actually fire on the target:
        self.shoot_target(target)
        self.reload_timer = self.time_to_reload

        # Change to firing sprite.
        self.state = 'fire'
        self.sprite = self.sprite_fire
        self.state_timer = 0.2

    def update(self, dt):
        # Override in derived class
        pass

    def shoot_target(self, target):
        # Override in derived class
        pass

######################################################################

class CannonTower(Tower):
    cannon_fire_sound = pyglet.resource.media('sounds/cannon.wav', streaming=False)

    def __init__(self, x, y):
        Tower.__init__(self)

        # set up sprites
        self.sprite_normal = Sprite(resource.texture('graphics/cannon_tower.png', 37, 26))
        self.sprite_fire = Sprite(resource.texture('graphics/cannon_tower_fire.png', 41, 31))
        self.sprite = self.sprite_normal

        # set up area of effect
        self.set_area_of_effect_radius(90)
        self.time_to_reload = 0.5

        self.position = Vec2d(x, y)
        self.shape = Rectangle(-16, -16, 32, 32)

    def shoot_target(self, target):
        self.cannon_fire_sound.play()
        target.take_damage(5)
        # rotate sprite to point at target
        r = target.position - self.position
        self.rotation = r.get_angle_degrees()

    def update(self, dt):
        self.reload_timer -= dt
        if self.state == 'fire':
            self.state_timer -= dt
            if self.state_timer <= 0:
                # Return to normal sprite.
                self.state = 'normal'
                self.sprite = self.sprite_normal
        
        if self.reload_timer <= 0: 
            # Look for a nearby robot to fire on.
            self.shoot()

######################################################################

class VirusTower(Tower):
    cannon_fire_sound = pyglet.resource.media('sounds/virus_tower.wav', streaming=False)
    
    def __init__(self, x, y):
        Tower.__init__(self)

        # set up sprites
        self.sprite_normal = Sprite(resource.texture('graphics/virus_tower.png', 20, 14))
        self.sprite_fire = Sprite(resource.texture('graphics/virus_tower_fire.png', 51, 47))
        self.sprite = self.sprite_normal

        # set up area of effect
        self.set_area_of_effect_radius(90)
        self.time_to_reload = 1.5

        self.position = Vec2d(x, y)
        self.shape = Rectangle(-16, -16, 32, 32)

    def shoot_target(self, target):
        self.cannon_fire_sound.play()
        target.take_damage(5)

    def update(self, dt):
        self.reload_timer -= dt
        if self.state == 'fire':
            self.state_timer -= dt
            if self.state_timer <= 0:
                # Return to normal sprite.
                self.state = 'normal'
                self.sprite = self.sprite_normal
        
        if self.reload_timer <= 0: 
            # Look for a nearby robot to fire on.
            self.shoot()

######################################################################

class LightningTower(Tower):
    cannon_fire_sound = pyglet.resource.media('sounds/lightning_tower.wav', streaming=False)

    def __init__(self, x, y):
        Tower.__init__(self)

        # set up sprites
        self.sprite_normal = Sprite(resource.texture('graphics/lightning_tower.png', 16, 10))
        self.sprite_fire = Sprite(resource.texture('graphics/lightning_tower_fire.png', 35, 10))
        self.sprite = self.sprite_normal

        # set up area of effect
        self.set_area_of_effect_radius(90)
        self.time_to_reload = 1.5

        self.position = Vec2d(x, y)
        self.shape = Rectangle(-10, -10, 38, 38)
        
    def shoot_target(self, target):
        self.cannon_fire_sound.play()
        target.take_damage(5)

    def update(self, dt):
        self.reload_timer -= dt
        if self.state == 'fire':
            self.state_timer -= dt
            if self.state_timer <= 0:
                # Return to normal sprite.
                self.state = 'normal'
                self.sprite = self.sprite_normal
        
        if self.reload_timer <= 0: 
            # Look for a nearby robot to fire on.
            self.shoot()

######################################################################

class GlueTower(Tower):
    cannon_fire_sound = pyglet.resource.media('sounds/glue_tower.wav', streaming=False)

    def __init__(self, x, y):
        Tower.__init__(self)

        # set up sprites
        self.sprite_normal = Sprite(resource.texture('graphics/glue_tower.png', 27, 23))
        self.sprite_fire = Sprite(resource.texture('graphics/glue_tower_fire.png', 27, 23))
        self.sprite = self.sprite_normal

        # set up area of effect
        self.set_area_of_effect_radius(90)
        self.time_to_reload = 1

        self.position = Vec2d(x, y)
        self.shape = Rectangle(-16, -16, 32, 32)

        self.rotation = 0

    def shoot_target(self, target):
        self.cannon_fire_sound.play()
        target.slow_down(0.5)
        # rotate sprite to point at target
        r = target.position - self.position
        self.rotation = r.get_angle_degrees()
            
    def update(self, dt):
        self.reload_timer -= dt
        if self.state == 'fire':
            self.state_timer -= dt
            if self.state_timer <= 0:
                # Return to normal sprite.
                self.state = 'normal'
                self.sprite = self.sprite_normal
        
        if self.reload_timer <= 0: 
            # Look for a nearby robot to fire on.
            self.shoot()
